Skip to content

Migrate the remainder of cirq-core to np.random.Generators - #8527

Open
arettig wants to merge 4 commits into
quantumlib:mainfrom
arettig:rsos_generator_pr3
Open

arettig wants to merge 4 commits into
quantumlib:mainfrom
arettig:rsos_generator_pr3

Conversation

@arettig

@arettig arettig commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Part of #8374

This replaces the usage of parse_random_state with the new parse_random_generator in cirq-core. Several tests required specific seeds to pass and these were fixed in several ways:

  • In cases where the test simply asserted that a given seed gave an expected output, the test was changed to assert that the same seed always produces the same output by running multiple times. One assertion to an expected output was kept to cover portability.
  • In some cases, specific seeds were found that yielded random unitaries with a desired property for a test - e.g. were in a certain portion of the Weyl chamber. These tests were fixed by simply searching for new seeds. Ideally these tests should not rely on randomness for this, but that can be fixed in a separate PR.
  • In some tests, results were compared too strictly for the statistical uncertainty, which passed previously because the seed was fixed. In these cases, the number of samples was increased to tighten the uncertainty sufficiently to avoid flakiness.

Potentially breaking behavior:

  • Outputs for a fixed seed will not reproduce results obtained before this PR.

Replaces the use of RandomState in the random circuit generators with
numpy Generators. Replaces cases where the tests depended on the
random seeding/algorithm.
@github-actions github-actions Bot added the size: L 250< lines changed <1000 label Nov 11, 2026
@codecov

codecov Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.58%. Comparing base (8a8fc5c) to head (80a317a).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8527      +/-   ##
==========================================
- Coverage   99.58%   99.58%   -0.01%     
==========================================
  Files        1137     1137              
  Lines      105119   105133      +14     
==========================================
+ Hits       104687   104698      +11     
- Misses        432      435       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@arettig
arettig marked this pull request as ready for review November 11, 2026 21:55
@arettig
arettig requested a review from a team as a code owner November 11, 2026 21:55

@pavoljuhas pavoljuhas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM after addressing some small issues (see inline) and a few leftover references to np.random.RandomState in touched files.

Thank you for this conversion!

qubits = cirq.LineQubit.range(num_qubits)
circuit = cirq.Circuit()
random_state = cirq.value.parse_random_state(random_state)
random_state = cirq.value.parse_random_generator(random_state)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - should we rename LHS to prng here?

@@ -33,7 +33,7 @@ def get_seeded_initial_mapping(graph_seed, init_seed) -> dict[cirq.Qid, cirq.Qid

@pytest.mark.parametrize('seed', [random.randint(0, 2**32) for _ in range(10)])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - can we replace the above with one static sample, i.e., a fixed list of 10 integers generated as above?

This would let us have constant parameterized test names (which include parameter value) with effectively the same test.



def test_benchmark_2q_xeb_fidelities_vectorized() -> None:
rs = np.random.RandomState(52)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can replace these instances of RandomState - the patch below seems to work:

diff --git a/cirq-core/cirq/experiments/xeb_fitting_test.py b/cirq-core/cirq/experiments/xeb_fitting_test.py
index bd1fb45c9..363e0681c 100644
--- a/cirq-core/cirq/experiments/xeb_fitting_test.py
+++ b/cirq-core/cirq/experiments/xeb_fitting_test.py
@@ -142,6 +142,8 @@ def test_benchmark_2q_xeb_fidelities_parallel() -> None:
 
 def test_benchmark_2q_xeb_fidelities_vectorized() -> None:
-    rs = np.random.RandomState(52)
-    mock_records = [{'pure_probs': rs.rand(4), 'sampled_probs': rs.rand(4)} for _ in range(100)]
+    prng = cirq.value.parse_random_generator(52)
+    mock_records = [
+        {'pure_probs': prng.random(4), 'sampled_probs': prng.random(4)} for _ in range(100)
+    ]
     df = pd.DataFrame(mock_records)
 
@@ -307,7 +309,7 @@ def test_parallel_full_workflow(request, use_pool) -> None:
 
 def test_fit_exponential_decays() -> None:
-    rs = np.random.RandomState(999)
+    prng = cirq.value.parse_random_generator(999)
     cycle_depths = np.arange(3, 100, 11)
-    fidelities = 0.95 * 0.98**cycle_depths + rs.normal(0, 0.2)
+    fidelities = 0.95 * 0.98**cycle_depths + prng.normal(0, 0.02)
     a, layer_fid, a_std, layer_fid_std = _fit_exponential_decay(cycle_depths, fidelities)
     np.testing.assert_allclose([a, layer_fid], [0.95, 0.98], atol=0.02)
@@ -317,7 +319,7 @@ def test_fit_exponential_decays() -> None:
 
 def test_fit_exponential_decays_negative_fids() -> None:
-    rs = np.random.RandomState(999)
+    prng = cirq.value.parse_random_generator(999)
     cycle_depths = np.arange(3, 100, 11)
-    fidelities = 0.5 * 0.5**cycle_depths + rs.normal(0, 0.2) - 0.5
+    fidelities = 0.5 * 0.5**cycle_depths + prng.normal(0, 0.2) - 0.5
     assert np.sum(fidelities > 0) <= 1, 'they go negative'
     a, layer_fid, a_std, layer_fid_std = _fit_exponential_decay(cycle_depths, fidelities)

Comment on lines +36 to +38
random_state: A seed (int) or `np.random.Generator` to use when
generating random values. If not set, a new unseeded generator is
used.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit on wording - feel free to adjust:

Suggested change
random_state: A seed (int) or `np.random.Generator` to use when
generating random values. If not set, a new unseeded generator is
used.
random_state: A seed (int) or `np.random.Generator` to use when
generating random values. Use a default unseeded generator
when unspecified.

Also please check and sync docstrings of other functions here that take random_state.

Comment on lines +63 to +65
rng: Random number generator (or seed) to be used in sampling. Anything
accepted by `cirq.value.parse_random_generator`. Default is a new,
unseeded `np.random.Generator`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
rng: Random number generator (or seed) to be used in sampling. Anything
accepted by `cirq.value.parse_random_generator`. Default is a new,
unseeded `np.random.Generator`.
rng: Random number generator (or seed) to be used in sampling. Anything
accepted by `cirq.value.parse_random_generator`. If unspecified,
use a default unseeded `np.random.Generator`.

"""
real_rng = random_state.parse_random_state(rng)
real_rng = random_state.parse_random_generator(rng)
size = tuple(shape)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - size can be confusing as it evokes a scalar. Let us just coerce shape or, if type checker complains, use a new variable tshape.

Suggested change
size = tuple(shape)
shape = tuple(shape)

Comment on lines 106 to 108
tab = two_qubit_gate_product_tabulation(
base_gate, 0.1, sample_scaling=2, random_state=np.random.RandomState(seed)
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tab = two_qubit_gate_product_tabulation(base_gate, 0.1, sample_scaling=2, random_state=seed)
)



def _cz_with_adjacent_z_rotations(
a: cirq.GridQubit, b: cirq.GridQubit, prng: np.random.RandomState

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
a: cirq.GridQubit, b: cirq.GridQubit, prng: np.random.Generator



def random_locals(x: float, y: float, z: float, seed=None) -> np.ndarray:
rng = np.random.RandomState(seed)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rng = cirq.value.parse_random_generator(seed)



def test_numerical_compiler_json_rejects_live_rng() -> None:
compiler = TwoQubitNumericalCompiler(base_gates=(_CZ,), random_state=np.random.RandomState(1))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
compiler = TwoQubitNumericalCompiler(base_gates=(_CZ,), random_state=np.random.default_rng())

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: L 250< lines changed <1000

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants